package com.tally.server.service;

import com.tally.server.domain.Income;
import com.tally.server.domain.IncomeExample;
import com.tally.server.dto.EverydayIncomeDTO;
import com.tally.server.dto.IncomeBatchDTO;
import com.tally.server.dto.IncomeDTO;
import com.tally.server.enums.IncomeTypeEnum;
import com.tally.server.mapper.IncomeMapper;
import com.tally.server.utils.CopyUtil;
import com.tally.server.utils.DateUtil;
import com.tally.server.utils.UUIDUtil;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;

@Service
public class IncomeService {
    private static final Logger logger = LoggerFactory.getLogger(IncomeService.class);

    @Autowired
    private IncomeMapper incomeMapper;

    /**
     * 保存，id有值时更新，无值时新增
     */
    public int save(IncomeDTO incomeDTO) {
        Income income = CopyUtil.copy(incomeDTO, Income.class);
        if (StringUtils.isEmpty(incomeDTO.getId())) {
            this.insert(income);
            return 1;
        } else {
            this.update(income);
            return 2;
        }
    }

    public void saveBatch(IncomeBatchDTO incomeBatchDTO) {
        Date recordStartDate = incomeBatchDTO.getRecordStartDate();
        Date recordEndDate = incomeBatchDTO.getRecordEndDate();
        List<Date> dateList = DateUtil.dateList(recordStartDate, recordEndDate);
        for (Date date : dateList) {
            Income income = CopyUtil.copy(incomeBatchDTO, Income.class);
            income.setRecordDate(date);
            insert(income);
        }
    }

    /**
     * 新增
     */
    private void insert(Income income) {
        Date now = new Date();
        income.setCreateTime(now);
        income.setUpdateTime(now);
        income.setId(UUIDUtil.getShortUUID());
        incomeMapper.insert(income);
    }

    /**
     * 更新
     */
    private void update(Income income) {
        income.setUpdateTime(new Date());
        incomeMapper.updateByPrimaryKey(income);
    }

    /**
     * 删除
     */
    public void delete(String id) {
        incomeMapper.deleteByPrimaryKey(id);
    }

    /**
     * 通过id查询
     * @param id
     * @return
     */
    public IncomeDTO selectById(String id){
        Income income = incomeMapper.selectByPrimaryKey(id);
        return CopyUtil.copy(income,IncomeDTO.class);
    }

    /**
     * 根据年月查询该月收入列表
     * @param yearI
     * @param monthI
     * @return
     */
    public List<IncomeDTO> getMonthIncome(Integer yearI, Integer monthI, String userId, boolean desc) {
        Date startDate = getStartDate(yearI,monthI);
        Date endDate = getEndDate(yearI,monthI);

        IncomeExample incomeExample = new IncomeExample();
        incomeExample.createCriteria()
                .andUserIdEqualTo(Integer.parseInt(userId))
                .andRecordDateBetween(startDate,endDate);
        if(desc){
            incomeExample.setOrderByClause("record_date desc,income_type");
        }else{
            incomeExample.setOrderByClause("record_date asc,income_type");
        }

        List<Income> incomeList = incomeMapper.selectByExample(incomeExample);
        return CopyUtil.copyList(incomeList, IncomeDTO.class);
    }

    /**
     * 得到某年的收入列表
     * @param yearI
     * @param userId
     * @return
     */
    public List<IncomeDTO> getYearIncome(Integer yearI, String userId, boolean asc) {
        Date startDate = getStartDate(yearI,1);
        Date endDate = getEndDate(yearI,12);

        IncomeExample incomeExample = new IncomeExample();
        incomeExample.createCriteria()
                .andUserIdEqualTo(Integer.parseInt(userId))
                .andRecordDateBetween(startDate,endDate);
        if(asc){
            incomeExample.setOrderByClause("record_date");
        }
        List<Income> incomeList = incomeMapper.selectByExample(incomeExample);
        return CopyUtil.copyList(incomeList,IncomeDTO.class);
    }

    /**
     * 得到某年某月的第一天
     * @param yearI
     * @param monthI
     * @return
     */
    private Date getStartDate(Integer yearI,Integer monthI){
        LocalDate startLocalDate = LocalDate.of(yearI,monthI,1);
        return DateUtil.localDate2Date(startLocalDate);
    }

    /**
     * 得到某年某月的最后一天
     * @param yearI
     * @param monthI
     * @return
     */
    private Date getEndDate(Integer yearI,Integer monthI){
        LocalDate endLocalDate = LocalDate.of(yearI,monthI,DateUtil.lastDayOf(yearI,monthI));
        return DateUtil.localDate2Date(endLocalDate);
    }

    /**
     * 得到某年收入之和
     * @param yearI
     * @param userId
     * @return
     */
    public BigDecimal getYearSum(Integer yearI, String userId) {
        logger.info("{}年收入求和",yearI);
        List<IncomeDTO> incomeDTOList = getYearIncome(yearI, userId, false);
        BigDecimal sum = incomeDTOList.stream().map(IncomeDTO::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
        return sum;
    }

    /**
     * 得到某年某月收入之和
     * @param yearI
     * @param monthI
     * @param userId
     * @return
     */
    public BigDecimal getMonthSum(Integer yearI, Integer monthI, String userId) {
        logger.info("{}年{}月收入求和",yearI,monthI);
        List<IncomeDTO> incomeDTOList = getMonthIncome(yearI, monthI, userId, true);
        BigDecimal sum = incomeDTOList.stream().map(IncomeDTO::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
        return sum;
    }

    /**
     * 得到某月中每天的收入总和，空补0
     * @param yearI
     * @param monthI
     * @param userId
     * @return
     */
    public List<BigDecimal> everydayIncomeSum(int yearI, int monthI, String userId) {
        List<IncomeDTO> monthIncomeList = getMonthIncome(yearI, monthI, userId, false);
        List<BigDecimal> list = new ArrayList<>();
        int days = DateUtil.lastDayOf(yearI, monthI);
        Date firstDay = DateUtil.localDate2Date(LocalDate.of(yearI,monthI,1));
        boolean exit = false;
        for(int i=0;i<monthIncomeList.size();){
            IncomeDTO incomeDTO = monthIncomeList.get(i);
            if(!incomeDTO.getRecordDate().equals(firstDay)){
                list.add(BigDecimal.ZERO);
            }else{
                BigDecimal sum = BigDecimal.ZERO;
                do{
                    sum = sum.add(incomeDTO.getMoney());
                    if(i+1 == monthIncomeList.size()){
                        exit = true;
                        break;
                    }
                    incomeDTO = monthIncomeList.get(++i);
                }while (incomeDTO.getRecordDate().equals(firstDay));
                list.add(sum);
            }
            firstDay = plusOneDay(firstDay);
            if(exit){
                break;
            }
        }
        while(list.size()<days){
            list.add(BigDecimal.ZERO);
        }
        return list;
    }

    /**
     * 日期增加一天
     * @param date
     * @return
     */
    private Date plusOneDay(Date date){
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, 1);
        return calendar.getTime();
    }

    /**
     * 得到某年某月各项收入之和
     * @param yearI
     * @param monthI
     * @param userId
     * @return
     */
    public Map<String, BigDecimal> eachIncomeSum(int yearI, int monthI, String userId) {
        logger.info("计算{}年{}月各项收入总和",yearI,monthI);
        List<IncomeDTO> incomeDTOList = getMonthIncome(yearI, monthI, userId, false);
        return getIncomeSumMap(incomeDTOList);
    }

    /**
     * 得到某年各项收入之和
     * @param yearI
     * @param userId
     * @return
     */
    public Map<String, BigDecimal> eachIncomeSum(int yearI, String userId) {
        logger.info("计算{}年各项收入总和",yearI);
        List<IncomeDTO> incomeDTOList = getYearIncome(yearI, userId, false);
        return getIncomeSumMap(incomeDTOList);
    }

    /**
     * 提取收入列表中的各项收入
     * @param incomeDTOList
     * @return 包含各项收入之和的map
     */
    private Map<String, BigDecimal> getIncomeSumMap(List<IncomeDTO> incomeDTOList) {
        Map<String, BigDecimal> map = new HashMap<>();
        for (IncomeTypeEnum incomeType : IncomeTypeEnum.values()) {
            map.put(incomeType.getCode(), BigDecimal.ZERO);
        }
        for (IncomeDTO incomeDTO : incomeDTOList) {
            String type = incomeDTO.getIncomeType();
            BigDecimal money = map.get(type);
            map.put(type, money.add(incomeDTO.getMoney()));
        }
        return map;
    }

    /**
     * 得到每月收入总和，空补0
     * @param yearI
     * @param userId
     * @return list
     */
    public List<BigDecimal> monthIncomeSum(int yearI, String userId) {
        logger.info("计算{}年各月总收入",yearI);
        List<BigDecimal> decimalList = new ArrayList<>();
        List<IncomeDTO> incomeDTOList = getYearIncome(yearI, userId, true);
        boolean exit = false;
        for(int i=0;i<incomeDTOList.size();){
            IncomeDTO incomeDTO = incomeDTOList.get(i);
            int curMonth;
            if((curMonth = DateUtil.getCalendar(incomeDTO.getRecordDate()).get(Calendar.MONTH)) == decimalList.size()){
                BigDecimal sum = BigDecimal.ZERO;
                do{
                    sum = sum.add(incomeDTO.getMoney());
                    if(i+1 == incomeDTOList.size()){
                        exit = true;
                        break;
                    }
                }while (curMonth == DateUtil.getCalendar((incomeDTO = incomeDTOList.get(++i)).getRecordDate()).get(Calendar.MONTH));
                decimalList.add(sum);
            }else{
                int size = decimalList.size();
                for(int j=0;j<curMonth-size;j++){
                    decimalList.add(BigDecimal.ZERO);
                }
            }
            if(exit){
                break;
            }
        }
        while(decimalList.size()<12){
            decimalList.add(BigDecimal.ZERO);
        }
        return decimalList;
    }

    public List<EverydayIncomeDTO> getEverydayList(Date startDate, Date endDate, Integer userId) {
        IncomeExample incomeExample = new IncomeExample();
        incomeExample.createCriteria()
                .andUserIdEqualTo(userId)
                .andRecordDateBetween(startDate, endDate);
        incomeExample.setOrderByClause("record_date asc");
        List<Income> incomeList = incomeMapper.selectByExample(incomeExample);

        if (CollectionUtils.isEmpty(incomeList)) {
            return null;
        }

        boolean exit = false;
        List<EverydayIncomeDTO> resultList = new ArrayList<>();
        Date currentDate = startDate;
        // 组装数据
        for (int i = 0; i < incomeList.size();) {
            Income income = incomeList.get(i);
            EverydayIncomeDTO everydayIncomeDTO = new EverydayIncomeDTO(currentDate);
            if (income.getRecordDate().equals(currentDate)) {
                // 如果日期相同则继续循环
                do {
                    // 从map里拿到对应type下的money然后求和
                    String typeCode = income.getIncomeType();
                    String typeDesc = IncomeTypeEnum.getDesc(typeCode);
                    BigDecimal money = everydayIncomeDTO.getTypeMoneyMap().get(typeDesc);
                    everydayIncomeDTO.getTypeMoneyMap().put(typeDesc, money.add(income.getMoney()));
                    everydayIncomeDTO.setSum(everydayIncomeDTO.getSum().add(income.getMoney()));

                    if (i + 1 == incomeList.size()) {
                        exit = true;
                        break;
                    }
                    income = incomeList.get(++i);
                } while (income.getRecordDate().equals(currentDate));
            }
            resultList.add(everydayIncomeDTO);
            if (exit) {
                break;
            }
            currentDate = DateUtils.addDays(currentDate, 1);
        }
        // 如果还未到endDate，把后面全补0
        while (currentDate.before(endDate)) {
            currentDate = DateUtils.addDays(currentDate, 1);
            EverydayIncomeDTO everydayIncomeDTO = new EverydayIncomeDTO(currentDate);
            resultList.add(everydayIncomeDTO);
        }
        return resultList;
    }
}
